// Copyright (c) 2010, Andrew "IceD" Kirilenko
// All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the COPYING file.

function ADT() {
  this.ts = 0;
  this.tz = 0;
  
  this.cacheInvalidate();
}

ADT.locale = undefined;
ADT.locales = {};

ADT.getAvailableLocales = function() {
  var locales = [];
  for (locale in ADT.locales) {
    locales.push(locale);
  }
  
  return locales;
}

ADT.getLocale = function() {
  if (typeof(ADT.locale) === 'undefined') {
    return undefined;
  }

  return ADT.locales[ADT.locale];
}

ADT.setLocale = function(name) {
  ADT.locale = name;
}

ADT.registerLocale = function(name, data) {
  ADT.locales[name] = data;
  ADT.locales[name].name = name;

  if (typeof(ADT.getLocale()) === 'undefined') {
    ADT.setLocale(name);
  }
}

ADT.formatDigits = function(d, n, fill) {
  if (typeof(fill) === 'undefined') {
    fill = '0';
  }

  var ds = d.toString();
  while (ds.length < n) {
    ds = fill + ds;
  }

  return ds;
}

ADT.isLeapYear = function(year) {
  return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0));
}

ADT.getDaysInMonth = function(year, month) {
  var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  return days[month - 1] + (month == 2 && ADT.isLeapYear(year) ? 1 : 0);
}

ADT.getDaysInYear = function(year) {
  return ADT.isLeapYear(year) ? 366 : 365;
}

ADT.getLocalTZOffset = function() {
  return -((new Date()).getTimezoneOffset() * 60);
}

ADT.fromTimestamp = function(ts) {
  var adt = new ADT();
  
  adt.setTimestamp(ts);
  adt.setTZOffset(ADT.getLocalTZOffset());

  return adt;
}

ADT.fromJSDate = function(date) {
  var adt = new ADT();

  adt.setTimestamp((date.getTime() / 1000) | 0);
  adt.setTZOffset(ADT.getLocalTZOffset());

  return adt;
}

ADT.now = function() {
  return ADT.fromJSDate(new Date());
}

ADT.prototype.getTimestamp = function() {
  return this.ts;
}

ADT.prototype.setTimestamp = function(ts) {
  this.ts = ts;
  this.cacheInvalidate();
}

ADT.prototype.getTZOffset = function() {
  return this.tz;
}

ADT.prototype.setTZOffset = function(tz) {
  this.tz = tz;
  this.cacheInvalidate();
}

ADT.prototype.isCacheFilled = function() {
  return typeof(this.cache) !== 'undefined';
}

ADT.prototype.cacheInvalidate = function() {
  this.cache = undefined;
}

ADT.prototype.cacheFill = function() {
  var ts = this.getTimestamp() + this.getTZOffset();

  var day_seconds = ts % (24 * 3600);
  var day_num = (ts / (24 * 3600)) | 0;
  var day_of_week = (day_num + 4) % 7;

  if (ts < 0 && day_seconds != 0) {
    day_seconds += 24 * 3600;
    day_num -= 1;
    day_of_week = (day_of_week + 6) % 7;
  }

  var hour = (day_seconds / 3600) | 0;
  var minute = ((day_seconds % 3600) / 60) | 0;
  var second = day_seconds % 60;

  var year = 1970;
  if (ts >= 0) {
    while (day_num >= ADT.getDaysInYear(year)) {
      day_num -= ADT.getDaysInYear(year);
      year += 1;
    }
  } else {
    year -= 1;
    while (day_num < -ADT.getDaysInYear(year)) {
      day_num += ADT.getDaysInYear(year);
      year -= 1;
    }

    day_num += ADT.getDaysInYear(year);
  }

  day_of_year = day_num + 1;

  var day = day_of_year;
  var month = 1;
  while (day > ADT.getDaysInMonth(year, month)) {
    day -= ADT.getDaysInMonth(year, month);
    month += 1;
  }

  this.cache = {
    year: year,
    month: month,
    day: day,
    day_of_year: day_of_year,
    day_of_week: day_of_week,
    hour: hour,
    minute: minute,
    second: second
  };
}

ADT.prototype.getYear = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.year;
}

ADT.prototype.getMonth = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.month;
}

ADT.prototype.getDay = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.day;
}

ADT.prototype.getDayOfYear = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.day_of_year;
}

ADT.prototype.getDayOfWeek = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.day_of_week;
}

ADT.prototype.getHour = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.hour;
}

ADT.prototype.getMinute = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.minute;
}

ADT.prototype.getSecond = function() {
  if (!this.isCacheFilled()) {
    this.cacheFill();
  }

  return this.cache.second;
}

ADT.prototype.strftime = function(f) {
  var res = f;

  // TODO: %E
  // TODO: %O

  res = res.replace(/%r/g, '%I:%M:%S %p');
  res = res.replace(/%R/g, '%H:%M');
  res = res.replace(/%T/g, '%H:%M:%S');
  res = res.replace(/%X/g, ADT.getLocale().formatTime);

  res = res.replace(/%c/g, ADT.getLocale().formatDateTime);
  res = res.replace(/%D/g, '%m/%d/%y');
  res = res.replace(/%F/g, '%Y-%m-%d');
  res = res.replace(/%x/g, ADT.getLocale().formatDate);

  res = res.replace(/%a/g, ADT.getLocale().shortDays[this.getDayOfWeek()]);
  res = res.replace(/%A/g, ADT.getLocale().days[this.getDayOfWeek()]);
  res = res.replace(/%d/g, ADT.formatDigits(this.getDay(), 2));
  res = res.replace(/%e/g, ADT.formatDigits(this.getDay(), 2, ' '));
  res = res.replace(/%j/g, ADT.formatDigits(this.getDayOfYear(), 3));
  res = res.replace(/%u/g, this.getDayOfWeek() == 0 ? '7' : this.getDayOfWeek().toString());
  res = res.replace(/%w/g, this.getDayOfWeek());

  // TODO: %U
  // TODO: %V
  // TODO: %W

  res = res.replace(/%b/g, ADT.getLocale().shortMonths[this.getMonth() - 1]);
  res = res.replace(/%B/g, ADT.getLocale().months[this.getMonth() - 1]);
  res = res.replace(/%h/g, ADT.getLocale().shortMonths[this.getMonth() - 1]);
  res = res.replace(/%m/g, ADT.formatDigits(this.getMonth(), 2));

  res = res.replace(/%C/g, ADT.formatDigits((this.getYear() / 100) | 0, 2));
  // TODO: %g
  // TODO: %G
  res = res.replace(/%y/g, ADT.formatDigits(this.getYear() % 100, 2));
  res = res.replace(/%Y/g, this.getYear().toString());

  res = res.replace(/%H/g, ADT.formatDigits(this.getHour(), 2));
  res = res.replace(/%k/g, ADT.formatDigits(this.getHour(), 2, ' '));
  res = res.replace(/%I/g, ADT.formatDigits(this.getHour() == 0 ? '12' : this.getHour() % 12, 2));
  res = res.replace(/%l/g, ADT.formatDigits(this.getHour() == 0 ? '12' : this.getHour() % 12, 2, ' '));
  res = res.replace(/%M/g, ADT.formatDigits(this.getMinute(), 2));
  res = res.replace(/%p/g, this.getHour() < 12 ? 'AM' : 'PM');
  res = res.replace(/%P/g, this.getHour() < 12 ? 'am' : 'pm');
  res = res.replace(/%S/g, ADT.formatDigits(this.getSecond(), 2));
  // TODO: %z
  // TODO: %Z

  // TODO: %s

  res = res.replace(/%n/g, '\n');
  res = res.replace(/%t/g, '\t');
  res = res.replace(/%%/g, '%');

  return res;
}
